package net.sf.nwn.jme3;

import java.io.IOException;

import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Matrix4f;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.RenderQueue.Bucket;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.Control;

public class EmitterBehavior implements Control {
	private ParticleCollection pc;

	private int maxParticles;
	private float[] life;
	private float[] position;
	private float[] velocity;

	private float partialBirth;

	// -- emitter properties
	private ColorRGBA colorStart;
	private ColorRGBA colorEnd;
	private float alphaStart;
	private float alphaEnd;
	private float sizeStart;
	private float sizeEnd;
	private float sizeStart_y;
	private float sizeEnd_y;
	private int frameStart;
	private int frameEnd;
	private float birthrate;
	private float lifeExp;
	private float mass;
	private float spread;
	private float particleRot;
	private float startVelocity;
	private float randvel;
	private float fps;
	private float xsize;
	private float ysize;
	private int blend;
	private int xgrid;
	private int ygrid;
	private int render;
	private int p2pType;
	private float grav;
	private float drag;
	private int inheritType;
	private int update;

	private String name;

	// -- end of emitter properties

	public EmitterBehavior() {
	}

	public EmitterBehavior(EmitterNode emitter, Node local, Material ap) {

		colorStart = emitter.getColorStart();
		colorEnd = emitter.getColorEnd();
		alphaStart = emitter.getAlphaStart();
		alphaEnd = emitter.getAlphaEnd();
		sizeStart = emitter.getSizeStart();
		sizeEnd = emitter.getSizeEnd();
		sizeStart_y = emitter.getSizeStart_y();
		sizeEnd_y = emitter.getSizeEnd_y();
		frameStart = emitter.getFrameStart();
		frameEnd = emitter.getFrameEnd();
		birthrate = emitter.getBirthrate();
		lifeExp = emitter.getLifeExp();
		mass = emitter.getMass();
		spread = emitter.getSpread();
		particleRot = emitter.getParticleRot();
		startVelocity = emitter.getVelocity();
		randvel = emitter.getRandvel();
		fps = emitter.getFps();
		xsize = emitter.getXsize();
		ysize = emitter.getYsize();
		blend = emitter.getBlend();
		xgrid = emitter.getXgrid();
		ygrid = emitter.getYgrid();
		render = emitter.getRender();
		p2pType = emitter.getP2pType();
		grav = emitter.getGrav();
		drag = emitter.getDrag();
		update = emitter.getUpdate();

		if (update == EmitterNode.UPDATE_SINGLE) {
			maxParticles = 1;
		} else {
			maxParticles = (int) Math.ceil(birthrate * lifeExp) + 5;
		}

		pc = new ParticleCollection(maxParticles, ap);
		pc.setQueueBucket(Bucket.Transparent);
		pc.setShadowMode(ShadowMode.Off);
		
		
        
		life = new float[maxParticles];
		position = new float[maxParticles * 3];
		velocity = new float[maxParticles * 3];

		if (emitter.getInherit() == 1) {
			inheritType = INHERIT_ALL;
		} else if (emitter.getInherit_local() == 1) {
			inheritType = INHERIT_WORLD;
		} else if (emitter.getInherit_part() == 1) {
			inheritType = INHERIT_POSITION;
		} else {
			inheritType = INHERIT_ALL;
		}

		for (int i = 0; i < maxParticles; i++) {
			life[i] = -1;
		}
		local.attachChild(pc);

		if (particleRot != 0 && render == EmitterNode.RENDER_MOTION_BLUR) {
			System.err.println("WARN: particleRot and motionBlur are not possible together - turning off particleRotation");
		}
		this.name = emitter.getName();

	}

	
	
	private Matrix4f eyeT = new Matrix4f();
	private Matrix4f eyeTI = new Matrix4f();
	private Vector3f velVec = new Vector3f();
	private Vector3f dirVec = new Vector3f();
	private Vector3f pos = new Vector3f();
	private float[] tcoords = new float[] { 0, 0, 0, 0.5f, 0.5f, 0.5f, 0.5f, 0 };

	private Camera camera;

	private static float BIRTH_GRID_SCALE = 0.01f;


	public void updateData(double passed) {
		
		if ( camera == null ) {
			return;
		}
		
		Matrix4f viewProjectionMatrix = camera.getViewMatrix();
		
		Quaternion qqq = viewProjectionMatrix.toRotationQuat();
		eyeT.copy(null);
		eyeT.setRotationQuaternion(qqq);
		
		
		//eyeT.setScale(1,1,1);
		
		
		eyeT.invert(eyeTI);

		pc.updateTransforms(eyeTI);
		Matrix4f local = pc.getLocalMatrix();
		Matrix4f localI = pc.getLocalMatrixI();
		Vector3f eyeVec = pc.getEyeVector();

		
		passed = (float) Math.min(passed, 0.05);
		partialBirth += passed * birthrate;
		
		for (int i = 0, o3 = 0; i < maxParticles; i++, o3 += 3) {
			pos.set(position[o3], position[o3 + 1], position[o3 + 2]);
			if (inheritType == INHERIT_WORLD) {
				pos = localI.mult(pos);
			} else if (inheritType == INHERIT_POSITION) {
				dirVec.set(pos);
				dirVec = localI.mult(dirVec);
				pos.set(dirVec);
			}

			if (life[i] < 0) {
				if ((update == EmitterNode.UPDATE_SINGLE) || partialBirth > 1) {
					partialBirth -= 1;
					life[i] = 0;
					pos.x = (float) ((Math.random() - 0.5) * xsize * BIRTH_GRID_SCALE);
					pos.y = 0;
					pos.z = (float) ((Math.random() - 0.5) * ysize * BIRTH_GRID_SCALE);
					double speed = startVelocity + (Math.random() - 0.5) * randvel * 2;
					double xangle = (Math.random() - 0.5) * spread;
					double yangle = (Math.random() - 0.5) * spread;
					double vx = Math.sin(xangle);
					double vz = Math.cos(xangle) + Math.cos(yangle);
					double vy = Math.sin(yangle);

					double d = speed / (Math.abs(vx) + Math.abs(vy) + Math.abs(vz));

					velocity[o3] = (float) (d * vx);
					velocity[o3 + 1] = (float) (d * vy);
					velocity[o3 + 2] = (float) (d * vz);

				}
			} else {
				if ( partialBirth < -1 ) {
					life[i] = -1;
					partialBirth++;
				} else if ((update == EmitterNode.UPDATE_SINGLE) || (life[i] < lifeExp)) {
					dirVec.set(0,0,(float)(-mass * passed * 10) );
					//dirVec = localI.mult(dirVec);
					velocity[o3] += dirVec.x;
					velocity[o3 + 1] += dirVec.y;
					velocity[o3 + 2] += dirVec.z;

					if (p2pType == EmitterNode.P2P_TYPE_GRAVITY) {
						float sum = Math.abs(pos.x) + Math.abs(pos.y) + Math.abs(pos.z);

						velocity[o3] -= passed * grav * pos.x / sum;
						velocity[o3 + 1] -= passed * grav * pos.y / sum;
						velocity[o3 + 2] -= passed * grav * pos.z / sum;
					}

					pos.x += velocity[o3] * passed;
					pos.y += velocity[o3 + 1] * passed;
					pos.z += velocity[o3 + 2] * passed;
					life[i] += passed;

					if (p2pType == EmitterNode.P2P_TYPE_GRAVITY) {
						// different sign - passed through zero
						if (pos.x * (pos.x - velocity[o3] * passed) < 0) {
							velocity[o3] *= drag;
							velocity[o3 + 1] *= drag;
							velocity[o3 + 2] *= drag;
						}
					}
				} else {
					life[i] = -1;
				}
			}
			float currentLife = life[i];

			if (currentLife < 0) {
				pc.updateParticle(i, 0, 0, 0, 0.0f, 0.0f, 0, 0.0f, 0.0f, 0.0f, 0.0f, tcoords, false);
			} else {
				float mature = currentLife / lifeExp;

				if (update == EmitterNode.UPDATE_SINGLE)
					mature = 0;

				ColorRGBA start = colorStart;
				ColorRGBA end = colorEnd;

				float r = start.r + (end.r - start.r) * mature;
				float g = start.g + (end.g - start.g) * mature;
				float b = start.b + (end.b - start.b) * mature;
				float a = alphaStart + (alphaEnd - alphaStart) * mature;

				float xs = sizeStart + (sizeEnd - sizeStart) * mature;
				float ys = xs;

				if (sizeStart_y != 0 || sizeEnd_y != 0) {
					ys = sizeStart_y + (sizeEnd_y - sizeStart_y) * mature;
				}

				float tx = 1.0f / xgrid;
				float ty = 1.0f / ygrid;

				int frame = frameStart + ((int) (fps * currentLife)) % (frameEnd + 1 - frameStart);

				float stx = tx * (frame % xgrid);
				float sty = tx * ((frame / xgrid) % ygrid);
				float rotation = currentLife * particleRot;

				tcoords[0] = stx;
				tcoords[1] = sty;
				tcoords[2] = stx;
				tcoords[3] = sty + ty;
				tcoords[4] = stx + tx;
				tcoords[5] = sty + ty;
				tcoords[6] = stx + tx;
				tcoords[7] = sty;

				boolean alignToWorldFloor = false;
				if (render == EmitterNode.RENDER_MOTION_BLUR) {
					velVec.set(velocity[o3], velocity[o3 + 1], velocity[o3 + 2]);
					float speed = velVec.length();

					velVec = local.mult(velVec);
					velVec.normalize();
					float dot = Math.abs(velVec.dot(eyeVec));
					float blurlen = 1 * speed;

					blurlen = Math.min(blurlen, speed * currentLife);
					ys = ys * dot + blurlen * (1 - dot);

					velVec = eyeTI.mult(velVec);
					velVec.z = 0;

					dirVec.set(0, 1, 0);

					rotation = velVec.angleBetween(dirVec);
					if (velVec.x > 0)
						rotation = -rotation;

				} else if (render == EmitterNode.RENDER_ALIGNED_TO_WORLD_Z) {
					alignToWorldFloor = true;
				} else if (render == EmitterNode.RENDER_LINKED) {
					//System.err.println("Render mode 'linked' not supported");
				}

				pc.updateParticle(i, pos.x, pos.y, pos.z, xs, ys, rotation, r, g, b, a, tcoords, alignToWorldFloor);
			}
			if (inheritType == INHERIT_WORLD ) {
				pos = local.mult(pos);
			} else if (inheritType == INHERIT_POSITION) {
				dirVec.set(pos);
				dirVec = local.mult(dirVec);
				pos.set(dirVec);
			}
			position[o3] = pos.x;
			position[o3 + 1] = pos.y;
			position[o3 + 2] = pos.z;
		}
		if ( partialBirth < -1 ) {
			// don't go into negative particle debt
			partialBirth = -1;
		}
		
		if ((update != EmitterNode.UPDATE_SINGLE) && partialBirth > 1) {
			System.err.println("Partial birth " + partialBirth);
		}
		
		/*
		Transform worldTransform = pc.getWorldTransform();
		Vector3f v = new Vector3f();
		final float[] distance = new float[maxParticles];
		Integer[] particleOrder = new Integer[maxParticles];
		for ( int i =0; i < maxParticles; i++ ) {
			particleOrder[i] = i;
			if ( life[i] < 0 ) {
				distance[i] = Float.POSITIVE_INFINITY;
			} else {
				int ip = i*3;
				v.set(position[ip+0],position[ip+1],position[ip+2]);
				// no idea why it works without the world transform ???
				//worldTransform.transformVector(v, v);
				distance[i] =camera.distanceToNearPlane(v);
			} 
		}
		Arrays.sort(particleOrder, new Comparator<Integer>() {
			@Override
			public int compare(Integer o1, Integer o2) {
				float d1 = distance[o1];
				float d2 = distance[o2];
				if ( d1 > d2 ) {
					return -1;
				} else if ( d1 < d2 ) {
					return 1;
				} else {
					return 0;
				}
			}
		});
		
		*/
		pc.afterUpdate();

	}

	/**
	 * Gets the birthrate.
	 * 
	 * @return Returns a float
	 */
	public float getBirthrate() {
		return birthrate;
	}

	/**
	 * Sets the birthrate.
	 * 
	 * @param birthrate
	 *            The birthrate to set
	 */
	public void setBirthrate(float birthrate) {
		this.birthrate = birthrate;
	}

	/**
	 * Gets the alphaEnd.
	 * 
	 * @return Returns a float
	 */
	public float getAlphaEnd() {
		return alphaEnd;
	}

	/**
	 * Sets the alphaEnd.
	 * 
	 * @param alphaEnd
	 *            The alphaEnd to set
	 */
	public void setAlphaEnd(float alphaEnd) {
		this.alphaEnd = alphaEnd;
	}

	/**
	 * Gets the alphaStart.
	 * 
	 * @return Returns a float
	 */
	public float getAlphaStart() {
		return alphaStart;
	}

	/**
	 * Sets the alphaStart.
	 * 
	 * @param alphaStart
	 *            The alphaStart to set
	 */
	public void setAlphaStart(float alphaStart) {
		this.alphaStart = alphaStart;
	}

	public static float ACTIVATION_RADIUS = 15f;

	public static int INHERIT_WORLD = 1;
	public static int INHERIT_POSITION = 2;
	public static int INHERIT_ALL = 3;

	@Override
	public void write(JmeExporter ex) throws IOException {
		// TODO Auto-generated method stub

	}

	@Override
	public void read(JmeImporter im) throws IOException {
		// TODO Auto-generated method stub

	}

	@Override
	public Control cloneForSpatial(Spatial spatial) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void setSpatial(Spatial spatial) {
		// TODO Auto-generated method stub

	}

	
	public void setEnabled(boolean enabled) {
		// TODO Auto-generated method stub

	}

	
	public boolean isEnabled() {
		// TODO Auto-generated method stub
		return true;
	}

	@Override
	public void update(float tpf) {
		updateData(tpf);
	}

	@Override
	public void render(RenderManager rm, ViewPort vp) {
		this.camera = vp.getCamera();
	}

	public String getName() {
		return name;
	}
	
}
